package com.andox.fn.inf.rpc.rest.extension;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.lang3.StringUtils;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

public class ExtensionLoader<T> {
	private Set<Class<?> /* object factory */> objectFactoryClassTable = Sets.newHashSet();
	private final Map<Class<?>, ObjectFactory<T>> objectFactorySet = Maps.newHashMap();
	
//	public static void registerObjectFactory(Class<?> interfaceClass, Class<?> objectFactory) {
//		Set<Class<?>> objectFactorySet = objectFactoryClassTable.get(interfaceClass);
//		if (objectFactorySet == null) {
//			objectFactorySet = new HashSet<Class<?>>();
//		}
//		
//		objectFactorySet.add(objectFactory);
//	}
	
//	T _load(String id) {
//		for (ObjectFactory<T> of : this.objectFactorySet) {
//			if (of.getIdentitys().contains(id)) {
//				return of.getObject(id);
//			}
//		}
//		
//		return null;
//	}
	
	private final String JAVA_SERVICE_LOADER_PATH = "/META-INF/service/";
	private final String CUSTOM_EXTENSION_LOADER_PATH = "/META-INF/extension/";
	
	private Class<T> type;
	
	private Map<String /** id **/, T> instanceTable = Maps.newConcurrentMap();
	
	private Set<Class<?>> wrapperClassSet;
	
	@SuppressWarnings("rawtypes")
	private static final Map<Class<?>, ExtensionLoader> extensionLoaderTable = Maps.newConcurrentMap();
	
	private volatile AtomicBoolean initialized = new AtomicBoolean(false);
	
	@SuppressWarnings("unchecked")
	public static <E> ExtensionLoader<E> getExtensionLoader(Class<E> clazz){
		if (!extensionLoaderTable.containsKey(clazz)) {
			if (!clazz.isInterface() || !clazz.isAnnotationPresent(SPI.class)) {
				throw new RuntimeException("before use ExtensionLoader.getExtensionLoader(" + clazz.getName() + "),"
						+ " the clazz must be interface, and annotated by SPI annotation.");
			}
			
			ExtensionLoader<E> e= new ExtensionLoader<E>(clazz);
			e.loadExtentionClass();
			extensionLoaderTable.put(clazz, e);
		}
		
		return extensionLoaderTable.get(clazz);
	}
	
	private ExtensionLoader(Class<T> type) {
		this.type = type;
	}
	
	public T getExtension(String id) {
		if (!instanceTable.containsKey(id)) {
			initObjectFactory();
			
			for (ObjectFactory<T> of : objectFactorySet.values()) {
				if (of.getIdentitys().contains(id)) {
					T t = of.getObject(id);
					t = this.decorate(t);
					this.instanceTable.put(id, t);
					break;
				}
			}
		}
		
		T t = instanceTable.get(id);
		if (t == null)
			throw new RuntimeException("Can't load " + id + " extension of class " + this.type.getName());
		return t;
	}
	
	private T decorate(T object) {
		return object;
	}
	
	@SuppressWarnings("unchecked")
	private void initObjectFactory() {
		for (Class<?> objectFactoryClass : this.objectFactoryClassTable) {
			ObjectFactory<T> objectFactory = objectFactorySet.get(objectFactoryClass);
			if (objectFactory == null) {
				objectFactory = (ObjectFactory<T>)this.instanceObject(objectFactoryClass);
				this.objectFactorySet.put(objectFactoryClass, objectFactory);
				objectFactory.init();
			}
			
		}
	}
	
	private Object instanceObject(Class<?> clazz) {
		Constructor<?>[] cs = clazz.getConstructors();
		for (Constructor<?> c : cs) {
			if (c.getParameters().length == 0) { 
				// default constructor
				c.setAccessible(true);
				try {
					Object t = c.newInstance();
					return t;
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
		}
		
		throw new RuntimeException("there are no default constructor for class " + clazz.getName());
	}
	
	private void loadExtentionClass(){
		if (!initialized.get()) {
			synchronized (this) {
				if (!initialized.get()) {
					List<String> pathList = Lists.newArrayList(JAVA_SERVICE_LOADER_PATH, CUSTOM_EXTENSION_LOADER_PATH);
					
					Set<Class<?>> result = Sets.newHashSet();
					for (String path : pathList) {
						Set<Class<?>> temp = this.loadExtensionClass(path);
						result.addAll(temp);
					}
					
					Set<Class<?>> temp = result;
					temp.addAll(this.objectFactoryClassTable);
					this.objectFactoryClassTable = temp;
				}
			}
		}
	}
	
	private Set<Class<?>> loadExtensionClass(String path) {
		Set<Class<?>> result = Sets.newHashSet();

		try {
			// path = path + type.getName();
			path = path + type.getName() + "ObjectFactory";
			InputStream is = ExtensionLoader.class.getResourceAsStream(path);
			if (is == null) {
				return result;
			}
			
			BufferedReader reader = new BufferedReader(new InputStreamReader(is, "utf-8"));
			String line;
			while ((line = reader.readLine()) != null) {
				String[] splits = StringUtils.split(line, "=");
//				if (splits.length == 0 || splits.length > 2) {
//					continue;
//				}
				
				//String id = StringUtils.trim(splits[0]);
				String clazzString = StringUtils.trim(splits[0]);
				Class<?> clazz = Class.forName(clazzString);
				if (!ObjectFactory.class.isAssignableFrom(clazz)) {
					throw new RuntimeException("class is not inherit from interface " + type.getName());
				}
				
				try {
					clazz.getConstructor(this.type);
					if (this.wrapperClassSet == null) {
						this.wrapperClassSet = Sets.newHashSet();
					}
					this.wrapperClassSet.add(clazz);
					// 包装类
				}catch (NoSuchMethodException e) {
					// 实现类
					result.add(clazz);
				}
			}
		}catch (Exception e) {
			throw new RuntimeException(e);
		}
		
		return result;
	}
}
